
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Windowsx.h>

#include "tp_typedef.h"
#include "tinyplugin.h"
#include "tp_key_code.h"
#include "tp_event.h"
#include "tp_display_win32.h"
#include "tp_memory.h"
#include "tp_globals.h"

#define DISPLAY_WIDTH 640
#define DISPLAY_HEIGHT 480

static char szClassName[] = "TinyPlugin";

typedef struct _PrivInfo
{
	HWND wnd;
	HBITMAP bitmap;
	TpColor* bits;
	TpColor* reverse;
	TpEvent event;
}PrivInfo;

static LRESULT WinPaint(HWND hWnd)
{
	PAINTSTRUCT ps;
	PrivInfo* priv = (PrivInfo*)GetWindowLong(hWnd, GWL_USERDATA);
	HDC hDC = NULL;
	HBITMAP old_bitmap = NULL;

	BeginPaint(hWnd, &ps);

	hDC = CreateCompatibleDC(ps.hdc);
	old_bitmap = (HBITMAP)SelectObject(hDC, priv->bitmap);
	BitBlt(ps.hdc, 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, hDC, 0, 0, SRCCOPY);

	SelectObject(hDC, old_bitmap);
	DeleteDC(hDC);

	EndPaint(hWnd, &ps);

	return 0;
}

static int s_key_map[0xff] = {0};
static void tp_key_map_init()
{
	#if 1
	s_key_map[0xBD] = TP_KEY_MINUS;
	s_key_map[0x20] = TP_KEY_SPACE;
	s_key_map[0xBB] = TP_KEY_EQUAL;
	s_key_map[0x08] = TP_KEY_BACKSPACE;
	s_key_map[0x09] = TP_KEY_TAB;
	s_key_map[0xC0] = TP_KEY_QUOTELEFT;
	s_key_map[0xDE] = TP_KEY_QUOTERIGHT;
	s_key_map[0xDB] = TP_KEY_BRACKETLEFT;
	s_key_map[0xDD] = TP_KEY_BRACKETRIGHT;
	s_key_map[0x0d] = TP_KEY_ENTER;
	s_key_map[0xBA] = TP_KEY_SEMICOLON;
	s_key_map[0x30] = TP_KEY_0;
	s_key_map[0x31] = TP_KEY_1; 
	s_key_map[0x32] = TP_KEY_2;
	s_key_map[0x33] = TP_KEY_3;
	s_key_map[0x34] = TP_KEY_4;
	s_key_map[0x35] = TP_KEY_5;
	s_key_map[0x36] = TP_KEY_6;
	s_key_map[0x37] = TP_KEY_7;
	s_key_map[0x38] = TP_KEY_8;
	s_key_map[0x39] = TP_KEY_9;
	s_key_map[0x70] = TP_KEY_F1; 
	s_key_map[0x71] = TP_KEY_F2;
	s_key_map[0x72] = TP_KEY_F3;
	s_key_map[0x73] = TP_KEY_F4;
	s_key_map[0x74] = TP_KEY_F5;
	s_key_map[0x75] = TP_KEY_F6;
	s_key_map[0x76] = TP_KEY_F7;
	s_key_map[0x77] = TP_KEY_F8;
	s_key_map[0x78] = TP_KEY_F9;
	s_key_map[0xBC] = TP_KEY_COMMA;
	s_key_map[0xBE] = TP_KEY_DOT;
	s_key_map[0xBF] = TP_KEY_SLASH;
	s_key_map[0x10] = TP_KEY_RIGHTSHIFT;
	s_key_map[0x11] = TP_KEY_LEFTCTRL;
	s_key_map[0x14] = TP_KEY_CAPSLOCK;
	s_key_map[0x40] = TP_KEY_LEFTALT;
	s_key_map[0x41] = TP_KEY_a;
	s_key_map[0x42] = TP_KEY_b;
	s_key_map[0x43] = TP_KEY_c;
	s_key_map[0x44] = TP_KEY_d;
	s_key_map[0x45] = TP_KEY_e;
	s_key_map[0x46] = TP_KEY_f;
	s_key_map[0x47] = TP_KEY_g;
	s_key_map[0x48] = TP_KEY_h;
	s_key_map[0x49] = TP_KEY_i;
	s_key_map[0x4a] = TP_KEY_j;
	s_key_map[0x4b] = TP_KEY_k;
	s_key_map[0x4c] = TP_KEY_l;
	s_key_map[0x4d] = TP_KEY_m;
	s_key_map[0x4e] = TP_KEY_n;
	s_key_map[0x4f] = TP_KEY_o;
	s_key_map[0x50] = TP_KEY_p;
	s_key_map[0x51] = TP_KEY_q;
	s_key_map[0x52] = TP_KEY_r;
	s_key_map[0x53] = TP_KEY_s;
	s_key_map[0x54] = TP_KEY_t;
	s_key_map[0x55] = TP_KEY_u;
	s_key_map[0x56] = TP_KEY_v;
	s_key_map[0x57] = TP_KEY_w;
	s_key_map[0x58] = TP_KEY_x;
	s_key_map[0x59] = TP_KEY_y;
	s_key_map[0x5a] = TP_KEY_z;
	s_key_map[0x21] = TP_KEY_PAGEUP;
	s_key_map[0x25] = TP_KEY_LEFT;
	s_key_map[0x27] = TP_KEY_RIGHT;
	s_key_map[0x24] = TP_KEY_HOME;
	s_key_map[0x23] = TP_KEY_END;
	s_key_map[0x28] = TP_KEY_DOWN;
	s_key_map[0x22] = TP_KEY_PAGEDOWN;
	s_key_map[0x2d] = TP_KEY_INSERT;
	s_key_map[0x2e] = TP_KEY_DELETE;
	s_key_map[0x26] = TP_KEY_UP;
	#endif
	return;
}

static LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PrivInfo* priv = (PrivInfo*)GetWindowLong(hWnd, GWL_USERDATA);

	/* CreateWindowEx()执行时会发送消息，此时USERDATA尚未制定，所以需要判断 */
	if (NULL != priv)
	{
		priv->event.type = TP_EVENT_NOP;
	}

	switch (message)
	{
		case WM_PAINT:
		{
			WinPaint(hWnd);
			break;
		}
		case WM_DESTROY:
		{
			tp_quit();
			PostQuitMessage(0);
			break;
		}
		case WM_LBUTTONDOWN:
		case WM_LBUTTONDBLCLK:
		case WM_MOUSEMOVE:
		{
			if (WM_LBUTTONDOWN == message) priv->event.type = TP_EVENT_LBUTTON_DOWN;
			else if(WM_LBUTTONUP == message) priv->event.type = TP_EVENT_LBUTTON_UP;
			else priv->event.type = TP_EVENT_MOUSE_MOVE;

			priv->event.u_event.mouse.x = GET_X_LPARAM(lParam);
			priv->event.u_event.mouse.y = GET_Y_LPARAM(lParam);

			break;
		}
		case WM_KEYDOWN:
		case WM_KEYUP:
		{
			priv->event.type = WM_KEYDOWN == message ? TP_EVENT_KEY_DOWN : TP_EVENT_KEY_UP;
			priv->event.u_event.key.code = s_key_map[wParam];

			break;
		}
		default:return DefWindowProc(hWnd, message, wParam, lParam);
	}

	/* 键盘和鼠标消息交由wnd_manager进行统一分发管理 */
	if (NULL != priv && TP_EVENT_NOP != priv->event.type)
	{
		tp_wnd_manager_on_event(tp_default_wnd_manager(), &priv->event);

        priv->event.type = TP_EVENT_NOP;
	}

	return 0;
}

static HWND create_display_window(void)
{
	HWND hWnd = NULL;
	WNDCLASSEX wincls;
	int width = DISPLAY_WIDTH + 2 * GetSystemMetrics(SM_CXFRAME);
	int height = DISPLAY_HEIGHT + 2 * GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION);

	wincls.cbSize = sizeof(WNDCLASSEX);
	wincls.style = 0;
	wincls.lpfnWndProc = WinProc;
	wincls.cbClsExtra = 0;
	wincls.cbWndExtra = 0;
	wincls.hInstance = NULL;
	wincls.hIcon = NULL;
	wincls.hCursor = LoadCursor(NULL, IDC_ARROW);
	wincls.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
	wincls.lpszMenuName = NULL;
	wincls.lpszClassName = szClassName;
	wincls.hIconSm = NULL;

	if (0 != RegisterClassEx(&wincls))
	{
		hWnd = CreateWindowEx(
			0,
			szClassName,
			"TinyPlugin",
			WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT,
			CW_USEDEFAULT,
			width,
			height,
			HWND_DESKTOP,
			NULL,
			NULL,
			NULL
			);
	}

	return hWnd;
}

static int tp_display_win32_update(TpDisplay* thiz, int xo, int yo, TpBitmap* bitmap, TpRect* rect)
{
	DECL_PRIV(thiz, priv);
	if (NULL != priv)
	{
		MSG msg;
		BITMAPINFO bmpInfo;
		int i, j;
		TpColor* src;
        TpColor* dst;

		if(TP_RET_OK != tp_bitmap_copy_to_data_bgra32(bitmap, rect, priv->bits, xo, yo, 
									  DISPLAY_WIDTH, DISPLAY_HEIGHT))
		{
			return TP_RET_FAIL;
		}

		/* 将像素逐行上下翻转到GID所需的顺序· */
		src = priv->bits;
		dst = priv->reverse + DISPLAY_WIDTH * (DISPLAY_HEIGHT - 1);
		for (i = 0; i < DISPLAY_HEIGHT; ++i)
		{
			for (j = 0; j < DISPLAY_WIDTH; ++j)
			{
				dst[j] = src[j];
			}

			dst -= DISPLAY_WIDTH;
			src += DISPLAY_WIDTH;
		}

		bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
		bmpInfo.bmiHeader.biWidth = DISPLAY_WIDTH;
		bmpInfo.bmiHeader.biHeight = DISPLAY_HEIGHT;
		bmpInfo.bmiHeader.biPlanes = 1;
		bmpInfo.bmiHeader.biBitCount = 32;
		bmpInfo.bmiHeader.biCompression = BI_RGB;

		SetDIBits(GetDC(priv->wnd), priv->bitmap, 0, DISPLAY_HEIGHT, priv->reverse, &bmpInfo, DIB_RGB_COLORS);
		InvalidateRect(priv->wnd, NULL, FALSE);

		while(PeekMessage(&msg, priv->wnd, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);

			if (WM_PAINT == msg.message) break;
		}
	}

	return TP_RET_OK;
}

static int tp_display_win32_width(TpDisplay* thiz)
{
	return DISPLAY_WIDTH;
}

static int tp_display_win32_height(TpDisplay* thiz)
{
	return DISPLAY_HEIGHT;
}

static void tp_display_win32_destroy(TpDisplay* thiz)
{
	DECL_PRIV(thiz, priv);
	if (NULL != priv)
	{
		DestroyWindow(priv->wnd);
		tp_free(priv->bits);
		tp_free(priv->reverse);
		DeleteObject(priv->bitmap);
		tp_free(thiz);
	}

	return;
}

TpDisplay* tp_display_win32_create(void)
{
	TpDisplay* thiz = (TpDisplay*)tp_alloc(sizeof(TpDisplay) * sizeof(PrivInfo));
	if (NULL != thiz)
	{
		DECL_PRIV(thiz, priv);

		thiz->width = tp_display_win32_width;
		thiz->height = tp_display_win32_height;
		thiz->update = tp_display_win32_update;
		thiz->destroy = tp_display_win32_destroy;

		priv->wnd = create_display_window();
		priv->bitmap = CreateBitmap(DISPLAY_WIDTH, DISPLAY_HEIGHT, 1, 32, NULL);
		priv->bits = (TpColor*)tp_alloc(DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(TpColor));
		priv->reverse = (TpColor*)tp_alloc(DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(TpColor));

		SetWindowLong(priv->wnd, GWL_USERDATA, (LONG)priv);
		ShowWindow(priv->wnd, SW_SHOW);
		UpdateWindow(priv->wnd);

        tp_key_map_init();
	}

	return thiz;
}
